/**
 * @description: 自定义 request 网络请求工具,基于axios
 * @author LiQingSong
 */
import { ElMessage } from "element-plus";
import qs from "qs";
import axios, { AxiosInstance, AxiosPromise, AxiosResponse, Canceler, RawAxiosRequestHeaders } from "axios";
import { ContentTypeEnum, ResultCodeEnum } from "@/enums/utils.request.enum";
import { ajaxHeadersTokenKey, ajaxHeadersRefreshTokenKey, ajaxResponseNoVerifyUrl } from "@/config/settings";
import { getToken, getRefreshToken, setToken } from "@/utils/localToken";
import { isFunction } from "@/utils/is";
import router from "@/config/router";
import { IResponseData, ICodeMessage, IAxiosRequestConfig } from "@/@types/utils.request";
import { queryRefreshToken } from "@/services/user";

/* ================ 自定义请求消除器 相关 S ======================= */
let requestPendingMap = new Map<string, Canceler>();

const getRequestPendingUrl = (config: IAxiosRequestConfig) => {
	let configData: any = typeof config.data === "string" ? JSON.parse(config.data || "{}") : config.data;
	return [config.method, config.url, qs.stringify(configData), qs.stringify(config.params)].join("&");
};

export class RequestCanceler {
	addPending(config: IAxiosRequestConfig) {
		this.removePending(config);
		const url = getRequestPendingUrl(config);
		config.cancelToken =
			config.cancelToken ||
			new axios.CancelToken((cancel) => {
				if (!requestPendingMap.has(url)) {
					requestPendingMap.set(url, cancel);
				}
			});
	}

	removePending(config: IAxiosRequestConfig, isCancel = true) {
		const url = getRequestPendingUrl(config);
		if (requestPendingMap.has(url)) {
			isCancel && requestPendingMap.get(url)?.();
			requestPendingMap.delete(url);
		}
	}

	removeAllPending() {
		requestPendingMap.forEach((cancel) => cancel && isFunction(cancel) && cancel());
		requestPendingMap.clear();
	}

	reset() {
		requestPendingMap.clear();
	}
}

export const requestCanceler = new RequestCanceler();

/* ================ 自定义请求类 相关 S ======================= */
const customCodeMessage: ICodeMessage = {
	[ResultCodeEnum.LOGININVALID]: "当前用户登入信息已失效，请重新登入再操作",
};

const serverCodeMessage: ICodeMessage = {
	0: "服务器成功返回请求的数据",
	400: "Bad Request",
	401: "Unauthorized",
	403: "Forbidden",
	404: "Not Found",
	500: "服务器发生错误，请检查服务器(Internal Server Error)",
	502: "网关错误(Bad Gateway)",
	503: "服务不可用，服务器暂时过载或维护(Service Unavailable)",
	504: "网关超时(Gateway Timeout)",
};

let promiseItem: Promise<boolean> | null = null;

const refreshToken = async () => {
	if (!promiseItem) {
		promiseItem = queryRefreshToken()
			.then((resp) => {
				resp.code === 0 && setToken(resp.data);
				return true;
			})
			.finally(() => {
				promiseItem = null;
			});
	}
	return promiseItem;
};

const errorHandler = async (error: any) => {
	const { response, message } = error;

	if (message === "CustomError") {
		const {
			config,
			data: { code, msg },
		} = response;
		const reqUrl = config.url.split("?")[0].replace(config.baseURL, "");
		if (!ajaxResponseNoVerifyUrl.includes(reqUrl)) {
			if (code === ResultCodeEnum.LOGININVALID) {
				const isSuccess = await refreshToken();
				if (isSuccess) {
					return ask.all(response.config);
				} else {
					ElMessage.warning(customCodeMessage[code] || msg || "Error");
					router.push("/user/login");
				}
			} else {
				ElMessage.warning(customCodeMessage[code] || msg || "Error");
			}
		}
	} else if (response) {
		const errorText = serverCodeMessage[response.status] || response.statusText;
		ElMessage.warning(`请求错误 ${response.status}: ${response.request.responseURL}\n${errorText}`);
	} else {
		ElMessage.warning("网络异常：您的网络发生异常，无法连接服务器");
	}

	return Promise.reject(error);
};

export class Request {
	ajax: AxiosInstance;

	public constructor(config: IAxiosRequestConfig) {
		const { contentType, headers = {}, ...otherConfig } = config;
		this.ajax = axios.create({
			...otherConfig,
			headers: {
				...(headers as RawAxiosRequestHeaders),
				"content-type": contentType || ContentTypeEnum.JSON,
			},
		});

		this.ajax.interceptors.request.use((axiosConfig) => {
			requestCanceler.addPending(axiosConfig);
			const headerToken = getToken();
			const headerRefreshToken = getRefreshToken();

			if (headerToken) {
				axiosConfig.headers[ajaxHeadersTokenKey] = headerToken;
			}
			if (headerRefreshToken) {
				axiosConfig.headers[ajaxHeadersRefreshTokenKey] = headerRefreshToken;
			}
			return axiosConfig;
		});

		this.ajax.interceptors.response.use((response: AxiosResponse<IResponseData>) => {
			requestCanceler.removePending(response.config, false);
			if (response.data.code !== ResultCodeEnum.SUCCESS) {
				return Promise.reject({ response, message: "CustomError" });
			}
			return response;
		});
	}

	all<T = any, R = AxiosResponse<T>>(config: IAxiosRequestConfig): AxiosPromise<R> {
		return this.ajax(config)
			.then((response: AxiosResponse) => response.data)
			.catch(errorHandler);
	}
}

const ask = new Request({
	baseURL: import.meta.env.VITE_APP_API_URL || "",
	withCredentials: false,
	timeout: 0,
});

export default function ajax<T = any>(config: IAxiosRequestConfig): AxiosPromise<AxiosResponse<T>> {
	return ask.all<T>(config);
}
